home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Pascal Super Library
/
Pascal Super Library (CW International)(1997).bin
/
LIBRARY
/
PASWIZ20
/
PASWIZ.DOC
< prev
next >
Wrap
Text File
|
1994-11-04
|
50KB
|
1,398 lines
The Pascal Wizard's Library
=-------------------------=
Version 2.0
PasWiz Copyright (c) 1990-1994 Thomas G. Hanlin III
This is PasWiz, a library of assorted routines for use with
Turbo Pascal, Quick Pascal, and compatible compilers. The PasWiz
collection is copyrighted, but may be distributed as long as the
following conditions are met:
All PasWiz files must be distributed together as a unit in
unmodified form. No files may be left out or added.
YOU USE THIS LIBRARY AT YOUR OWN RISK. It has been tested by me
on my own computer, but I will not assume any responsibility for
any problems which PasWiz may cause you.
Shareware operates on a "try before you buy" basis. It is
expected that if you find PasWiz useful, you will register your
copy. You may not use PasWiz routines in programs intended for
distribution unless you have registered. Registration entitles
you to receive full source code for PasWiz and a great many
other products. See the ORDER.FRM file for details.
Table of Contents page 2
Overview and Legal Info .................................... 1
Archive Directories ........................................ 3
BCD Math ................................................... 5
Equipment Info ............................................. 9
Expression Evaluator ...................................... 12
Extensions to Pascal's Math ............................... 13
Joystick Support .......................................... 15
Keyboard Control .......................................... 16
String Stuff .............................................. 20
Music ..................................................... 24
Mouse ..................................................... 25
Archive Directories page 3
Unit: Archives
When I started in the microcomputer industry, there was a small
variety of file archivers, all (more or less) compatible. They
did not provide compression, which was relegated to another
large selection of more-or-less compatible utilities. Then came
SEA's ARC. It was very slow, but it did compression as well as
archiving, and included CRC checks so you could know whether the
files were intact. It swept the BBS scene in short order,
becoming the new standard. A few other archivers competed on
about a level footing, providing only minor variances on the ARC
theme. Then SEA decided to sue one of their more successful
competitors, Phil Katz (PKARC). The end result was PKZIP, which
totally blew ARC away-- a useful hint to companies that choose
to litigate instead of innovate. In the chaos resulting from the
breaking of the former ARC standard, many other archivers came
into being: ARJ, LZH, PAK, ZOO, et al.
PBWiz helps resolve the confusion by providing a single set of
routines which allow you to view the contents of archives in any
of the above-mentioned formats: ARJ, LZH, PAK, ZIP, ZOO, and
even the antique ARC protocol. It also handles self-extracting
EXE files of the forms produced by ARJ, LHARC and ZIP2EXE. Only
archive directories are provided at this time. Other formats
will also be added as they arise. If you have details on the
format of an archive that you'd like me to add to PBWiz, please
send them my way.
Viewing archive directories is handled in roughly the same
fashion as you might view a DOS file directory. This makes it
possible to treat an archive and a subdirectory in a similar
manner, as long as the archive doesn't contain archives itself.
When you're looking for the first file in an archive, use the
FindFirstA function. You must specify the archive name and a
file name. The archive name may include a drive and path
specification, and does not need to have the archive extension.
If you leave off the extension, FindFirstA will use the first
archive it comes across that matches the rest of the
specification. Note that the archive specification may not
contain wildcards. In contrast, the search file name may not
contain drive or path specs, but may contain wildcards.
PROCEDURE FindFirstA (ArchiveName, FileName: String;
VAR ErrCode: Integer);
Archive Directories page 4
Unit: Archives
If there are no files to be found, or if the archive
specification was bad, an error code will be returned. If there
was no error, there may well be more files to be found. You can
find each of them with FindNextA:
FindNextA (VAR ErrCode: Integer);
Of course, just finding a matching file doesn't do you much good
unless you can retrieve information about it. You can use any of
the following routines to provide information about a matched
file:
FUNCTION GetNameA: String; { file name }
FUNCTION GetDateA: String; { file date }
FUNCTION GetTimeA: String; { file time (24-hour) }
FUNCTION GetCRCA: String; { file CRC (8-char hexadecimal) }
FUNCTION GetStoreA: String; { file storage method }
PROCEDURE GetSizeA (VAR OriginalSize: LongInt;
VAR CurrentSize: LongInt);
When you're done viewing an archive, be sure to close it:
PROCEDURE CloseA;
Let's try an example, to view all files in an archive:
USES
Archives;
VAR
ErrCode: Integer;
BEGIN
IF ParamCount = 1 THEN BEGIN
FindFirstA(ParamStr(1), '*.*', ErrCode)
WHILE ErrCode = 0 DO BEGIN
WriteLn(GetNameA);
FindNextA(ErrCode);
END;
CloseA;
END
ELSE
WriteLn('Archive name expected');
END.
BCD Math page 5
Unit: BCD
Some of you may not have heard of BCD math, or at least not have
more than a passing acquaintance with the subject. BCD (short
for Binary-Coded Decimal) is a way of encoding numbers. It
differs from the normal method of handling numbers in several
respects. On the down side, BCD math is much slower than normal
math and the numbers take up more memory. However, the benefits
may far outweigh these disadvantages, depending on your
application: BCD math is absolutely precise within your desired
specifications, and you can make a BCD number as large as you
need. If your applications don't require great range or
precision out of numbers, normal Pascal math is probably the
best choice. For scientific applications, accounting,
engineering and other demanding tasks, though, BCD may be just
the thing you need.
The BCD math routines provided by PasWiz allow numbers of up to
255 digits long (the sign counts as a digit, but the decimal
point doesn't). You may set the decimal point to any position
you like, as long as there is at least one digit position to the
left of the decimal.
Since Pascal doesn't support BCD numbers directly, we store the
BCD numbers in strings. The results are not in text format and
won't mean much if displayed. A conversion routine allows you to
change a BCD number to a text string in any of a variety of
formats.
Note that the BCD math handler doesn't yet track
overflow/underflow error conditions. If you anticipate that this
may be a problem, it would be a good idea to screen your input
or to make the BCD range large enough to avoid these errors.
Let's start off by considering the BCD range. This is kept in
two integer variables, LDigits and RDigits, which can be
accessed by your program. These variables specify the maximum
number of digits to the left and to the right of the decimal
point. There must be at least one digit on the left, and the
total number of digits must be less than 255. The BCD strings
will have a length that's one larger than the total number of
digits, to account for the sign of the number. The decimal point
is implicit and doesn't take up any extra space.
It is assumed that you will only use one size of BCD number in
your program-- there are no provisions for handling mixed-length
BCD numbers. Of course, you could manage that yourself with a
little extra work, if it seems like a useful capability. If you
don't change LDigits or RDigits, the default size of the BCD
numbers will be 32 (20 to the left, 11 to the right, 1 for the
sign).
BCD Math page 6
Unit: BCD
Before doing any BCD calculations, you must have some BCD
numbers! The BCDSet routine takes a number in text string form
and converts it to BCD:
TextSt := '1234567890.50';
Nr := BCDSet(TextSt);
{ FUNCTION BCDSet (TextSt: String): String; }
If your numbers are stored as actual numbers, you can convert
them to a text string with Pascal's Str procedure, then to BCD:
Str(Number, TextSt);
Nr := BCDSet(TextSt);
BCD numbers can also be converted back to text strings, of
course. You may specify how many digits to the right of the
decimal to keep (the number will be truncated, not rounded). If
the RightDigits value is positive, trailing zeros will be kept;
if negative, trailing zeros will be removed. There are also
various formatting options which may be used. Here's how it
works:
FUNCTION BCDFormat (Nr: String; HowToFormat,
RightDigits: Integer): String;
The HowToFormat value may be any combination of the following
(just add the numbers of the desired formats together):
0 plain number
1 use commas to separate thousands, etc
2 start number with a dollar sign
4 put the sign on the right instead of the left side
8 use plus sign instead of space if nr is not negative
The BCD math functions are pretty much self-explanatory, so I'll
keep the descriptions brief. The single-parameter functions are
listed on the next page.
BCD Math page 7
Unit: BCD
{ absolute value }
FUNCTION BCDAbs (Nr: String): String;
{ cosine }
FUNCTION BCDCos (Nr: String): String;
{ cotangent }
FUNCTION BCDCot (Nr: String): String;
{ cosecant }
FUNCTION BCDCsc (Nr: String): String;
{ convert degrees to radians }
FUNCTION BCDDeg2Rad (Nr: String): String;
{ the constant "e" }
FUNCTION BCDe: String;
{ factorial }
FUNCTION BCDFact (Nr: Integer): String;
{ fractional part of number }
FUNCTION BCDFrac (Nr: String): String;
{ integer part of number }
FUNCTION BCDInt (Nr: String): String;
{ negation }
FUNCTION BCDNeg (Nr: String): String;
{ the constant "pi" }
FUNCTION BCDpi: String;
{ convert radians to degrees }
FUNCTION BCDRad2Deg (Nr: String): String;
{ secant }
FUNCTION BCDSec (Nr: String): String;
{ signum }
FUNCTION BCDSgn (Nr: String): Integer;
{ sine }
FUNCTION BCDSin (Nr: String): String;
{ square root }
FUNCTION BCDSqrt (Nr: String): String;
{ tangent }
FUNCTION BCDTan (Nr: String): String;
BCD Math page 8
Unit: BCD
Notes on the single-parameter functions:
The signum function returns an integer based on the sign of
the BCD number:
-1 if the BCD number is negative
0 if the BCD number is zero
1 if the BCD number is positive
BCDpi is accurate to the maximum level afforded by the BCD
functions. BCDe is accurate to as many as 115 decimal places.
The actual accuracy, of course, depends on the size of BCD
numbers you've chosen.
The trigonometric functions (cos, sin, tan, sec, csc, cot)
expect angles in radians. BCDDeg2Rad and BCDRad2Deg will allow
you to convert back and forth between radians and degrees.
Here is a list of the two-parameter functions:
{ addition }
FUNCTION BCDAdd (Nr1, Nr2: String): String;
{ divide 1st by 2nd }
FUNCTION BCDDiv (Nr1, Nr2: String): String;
{ multiplication }
FUNCTION BCDMul (Nr1, Nr2: String): String;
{ subtract 2nd from 1st }
FUNCTION BCDSub (Nr1, Nr2: String): String;
{ raise to a power }
FUNCTION BCDPower (Nr: String; Power: Integer): String;
{ compare two numbers }
FUNCTION BCDCompare (Nr1, Nr2: String): Integer;
The comparison function returns an integer which reflects how
the two numbers compare to each other:
-1 Nr1 < Nr2
0 Nr1 = Nr2
1 Nr1 > Nr2
Equipment Info page 9
Unit: Equipment
The equipment unit gives you information about the computing
environment. This includes both installed software and hardware.
The first function allows you to determine if an "enhanced"
keyboard (101-key) is installed. It may not be able to figure
out what the keyboard is on some older not-quite-clone PCs, in
which case it will take the safe way out and report that there
is no enhanced keyboard. This function returns -1 if there is an
enhanced keyboard present, 0 if not.
FUNCTION EnhKbd: Integer;
Want to know the type of processor (CPU) being used? Can do!
FUNCTION Processor: Integer;
The results will be reported as a number which can be decoded as
follows:
0 NEC V20
1 8088 or 8086
2 80186
3 80286
4 80386
5 80486
Maybe you'd like to check for a CD-ROM drive:
FUNCTION CDROM: Integer;
This tells you how many logical drives exist, if there is a
CD-ROM available. If not, it will return 0. Note that the CD-ROM
installation check conflicts with the GRAPHICS.COM installation
check for DOS 4.0, due to some screw-up at IBM or Microsoft. I'm
not yet sure whether DOS 5.0 is similarly afflicted.
The number of floppy drives installed is retrieved with this:
FUNCTION Floppies: Integer;
To get the number of serial (COM) ports and parallel (LPT)
ports, use the following functions:
FUNCTION CommPorts: Integer;
FUNCTION PrtPorts: Integer;
Equipment Info page 10
Unit: Equipment
Now, there may be up to four floppy drives in a system; however,
the AT CMOS data area only directly supports two. This makes it
easy to find out what kind of drives the first two are, but not
the second two, if any. Such is life.
PROCEDURE FloppyType (VAR Drive1, Drive2: Integer);
The results from FloppyType are returned as follows:
0 no drive
1 5 1/4" 360K
2 5 1/4" 1.2M
3 3 1/2" 720K
4 3 1/2" 1.44M
5 3 1/2" 2.88M
Result codes of 6-7 are also available, but are not yet defined.
New memory types sure have burgeoned over the years... expanded,
extended, and now XMS. There are routines to check all of these:
{ amount of extended memory installed }
FUNCTION AllExtMem: LongInt;
{ BIOS extended memory available }
FUNCTION GetExtM: LongInt
{ amount of expanded memory (a page is 16 Kbytes) }
PROCEDURE GetEMSm (VAR TotalPages, FreePages: Integer);
{ amount of XMS memory (returned in kilobytes) }
PROCEDURE GetXMSm (VAR LargestFreeBlock, TotalFree: LongInt);
When you're dealing with extended memory, whether it be
BIOS-type or using the XMS standard, the results are returned in
kilobytes. Multiply 'em by 1024 to convert to bytes. When you're
dealing with expanded memory (EMS), the results are in pages of
16,384 bytes.
Equipment Info page 11
Unit: Equipment
A few more routines to get the versions of the EMS and XMS
drivers, if any:
PROCEDURE GetEMSv (VAR MajorV, MinorV: Integer);
PROCEDURE GetXMSv (VAR MajorV, MinorV: Integer);
These return the major and minor version numbers as two separate
integers. For example, EMS 4.0 would return major version 4,
minor version 0.
It's nice to know a little about the operating environment. With
the below routines, you can find out what the DOS version is;
what version of 4DOS, if any, is in use; and whether Microsoft
Windows is running.
PROCEDURE GetDOSv (VAR MajorV, MinorV: Integer);
PROCEDURE Get4DOSv (VAR MajorV, MinorV: Integer);
PROCEDURE WinCheck (VAR MajorV, MinorV: Integer);
These return results as major and minor version numbers, as
discussed above. The Get4DOSv and WinCheck routines return
zeroes if 4DOS and Windows, respectively, are not in use.
There are a couple of curious features of GetDOSv to keep in
mind. If the version is 10 or higher, you're running under OS/2.
DOS version 10 is actually OS/2 1.0, version 20 is OS/2 2.0, and
so on. Secondly, if you're using DOS 5.0, the version reported
may not be 5.0-- DOS 5.0 can be told to reply with a lower
version number to allow badly written older software to run
properly.
One final routine that should be of some value is the one that
allows you to find out what kind of display is available. It
tells you the specific adapter and whether the display is color
or monochrome. There is one case in which it can be confused,
however-- if the adapter is CGA, the display is assumed to be
color, since there is no way for the computer to know any
differently. So, although this routine provides a good idea of
what is available, it would be a good idea to provide an option
to tell the program that a monochrome display is attached.
Microsoft normally uses "/B" for this purpose, so that might be
a good standard to stick with.
PROCEDURE GetDisplay (VAR Adapter: Integer; VAR Mono: Boolean);
The adapter will be one of the following:
1 MDA 4 EGA
2 Hercules 5 MCGA
3 CGA 6 VGA
Expression Evaluator page 12
Unit: ExtMath
The expression evaluator solves numeric equations. It allows you
to find the result of an expression contained in a string.
Normal algebraic precedence is used, e.g. 4+3*5 evaluates to 19.
The usual numeric operators (*, /, +, -, ^) are supported
(multiply, divide, add, subtract, and raise to a power).
Negation is also supported. Parentheses can be used for
overriding the default order of operations.
You may use either double asterisk ("**") or caret ("^") symbols
to indicate exponentiation.
The constant PI is recognized, as are the following functions:
ABS absolute value INT integer
ACOS inverse cosine LOG natural log
ASIN inverse sine SIN sine
ATAN inverse tangent SQRT square root
COS cosine TAN tangent
FRAC fraction
Functions should not be nested.
Trig functions expect angles in radians.
To evaluate an expression, you pass it to the evaluator as a
string. You will get back either an error code or a real result.
See the CALC.PAS program for a working example.
PROCEDURE Evaluate (Expression: String; VAR Result: Real;
VAR ErrCode: Integer);
An expression evaluator adds convenience to any program that
needs to accept numbers. Why make someone reach for a calculator
when number crunching is what a computer does best?
Extensions to Pascal's Math page 13
Unit: ExtMath
For the most part, the math routines in this library are
designed to provide alternatives to the math routines that come
with Pascal. Still, Pascal's own math support is quite adequate
for many purposes, so there's no sense in ignoring it. Here are
some functions which improve on Pascal's math.
{ inverse cosine }
FUNCTION ArcCos (Number: Real): Real;
{ 1.0 >= Number >= -1.0 }
{ inverse hyperbolic cosine }
FUNCTION ArcCosH (Number: Real): Real;
{ inverse cotangent }
FUNCTION ArcCot (Number: Real): Real;
{ inverse hyperbolic cotangent }
FUNCTION ArcCotH (Number: Real): Real;
{ inverse cosecant }
FUNCTION ArcCsc (Number: Real): Real;
{ inverse hyperbolic cosecant }
FUNCTION ArcCscH (Number: Real): Real;
{ inverse secant }
FUNCTION ArcSec (Number: Real): Real;
{ inverse hyperbolic secant }
FUNCTION ArcSecH (Number: Real): Real;
{ inverse sine }
FUNCTION ArcSin (Number: Real): Real;
{ 1.0 >= Number >= -1.0 }
{ inverse hyperbolic sine }
FUNCTION ArcSinH (Number: Real): Real;
{ inverse hyperbolic tangent }
FUNCTION ArcTanH (Number: Real): Real;
{ ceiling: smallest integer >= specified number }
FUNCTION Ceil (Number: Real): Real;
{ hyperbolic cosine }
FUNCTION CosH (Number: Real): Real;
{ cotangent }
FUNCTION Cot (Number: Real): Real;
Extensions to Pascal's Math page 14
Unit: ExtMath
{ cosecant }
FUNCTION Csc (Number: Real): Real;
{ convert degrees to radians }
FUNCTION Deg2Rad (Number: Real): Real;
{ the constant "e" }
FUNCTION e: Real;
{ error function }
FUNCTION Erf (Number: Real): Real;
{ factorial }
FUNCTION Fact (Number: Integer): Real;
{ floor: largest integer <= specified number }
FUNCTION Floor (Number: Real): Real;
{ log (base 10) }
FUNCTION Log (Number: Real): Real;
{ convert radians to degrees }
FUNCTION Rad2Deg (Number: Real): Real;
{ raise a number to a power }
FUNCTION Raise (Number: Real; Power: Integer): Real;
{ secant }
FUNCTION Sec (Number: Real): Real;
{ hyperbolic secant }
FUNCTION SecH (Number: Real): Real;
{ signum (integer) }
FUNCTION SgnI (Number: Integer): Integer);
{ signum (real) }
FUNCTION SgnR (Number: Real): Integer);
{ hyperbolic sine }
FUNCTION SinH (Number: Real): Real;
{ tangent }
FUNCTION Tan (Number: Real): Real;
{ hyperbolic tangent }
FUNCTION TanH (Number: Real): Real;
Joystick Support page 15
Unit: Joystick
There's little enough to say about the joystick. A PC may have
up to two of them. Normally, a joystick has two buttons, and
returns a pair of coordinates which describe the position in
which it is being held. The coordinates vary depending on the
individual joystick, the computer involved, and other factors,
so it is wise to provide a calibration routine to customize your
program for the joystick(s) involved.
The FlightStick joystick has a dial for throttle control, which
is available only in a one-joystick setup. The throttle value is
returned as the Y coordinate of an imaginary second joystick.
Coordinates (X, Y), where X is the horizontal and Y the vertical
coordinate, range from around 0 to around 150 on my FlightStick.
Since these values may vary significantly, however, they are
returned as words.
The state of the joystick buttons may be checked individually or
all at once. If you need to know the state of multiple buttons,
it is faster to do it all at once, but you may do it either way.
FUNCTION ButtonA1: Boolean; { button 1 on 1st joystick }
FUNCTION ButtonA2: Boolean; { button 2 on 1st joystick }
FUNCTION ButtonB1: Boolean; { button 1 on 2nd joystick }
FUNCTION ButtonB2: Boolean; { button 2 on 2nd joystick }
PROCEDURE Buttons (VAR A1, A2, B1, B2: Boolean); { all buttons }
The joystick positions are handled internally by a rather
unfortunate method involving a timer, due to the way IBM
designed the joystick interface. This makes getting the position
rather slow. To alleviate the problem, all positions are
returned at once.
PROCEDURE Positions (VAR AX, AY, BX, BY: Word);
Joystick support is handled through a set of BIOS routines which
were added to the PC around 1985. These routines are not present
in some of the oldest PCs.
Keyboard Control page 16
Unit: Keyboard
The keyboard is not a particularly exciting or glamorous device.
In fact, we tend to forget about it except when it gets in the
way. Sometimes it's a hardware problem-- squishy or clacking
keys, or perhaps a commonly-used key placed in an out-of-the-way
location. Then again, sometimes it's the software that's the
problem. There are many aspects of keyboard control, not all of
which are necessarily related to input. This unit will let you
handle the keyboard in ways you may not have realized were
possible. Better yet, it can help make keyboard control easier
than the users of your programs dreamed possible.
Let's start out with keyboard output. Yep, not input-- output.
We can stuff up to 15 keys into the keyboard buffer. Why would
we ever want to do this? Perhaps to allow your program to pop-up
a TSR automatically, to start another program after your program
ends, or for creating key macros. You can enter extended key
codes (like function keys) by using CHR$(0) before the scan
code.
PROCEDURE TypeIn (Keys: String);
The usual keyboard action is quite sluggish. We can make it a
lot crisper by changing the key repeat rate and the delay before
repeating. This will work on ATs, but not PC/XT systems.
PROCEDURE SpeedKey (RepDelay, RepRate: Integer);
The delay may be 0-3 (1 by default):
0 250 milliseconds
1 500 milliseconds
2 750 milliseconds
3 1 second
The repeat rate may be 0-31 (11 by default). The larger the
number, the slower the speed-- 0 is around 30 cps, and 31 is
around 2 cps.
I generally prefer to have the keyboard cranked up to full
speed, using RepDelay and RepRate both set to zero. This may be
a bit too zippy for some people, and may cause keyboard buffer
overflows with some games. Experiment with it to see what you
like best.
Of course, there may be reasons to make keyboard repeat less
sensitive instead! That might be a good idea in programs written
for small children, for example. You can adjust the keyboard
equally well in either direction.
Keyboard Control page 17
Unit: Keyboard
Pascal allows you to control one of the keys which can interrupt
your program, namely the Break key. There's another dangerous
key which PasWiz allows you to control-- the PrtSc (PrintScreen)
key. PrtSc may not seem like a hazard at first glance, but if
it's pressed by accident with no printer ready, or in a graphics
mode which PrtSc doesn't understand how to print, the results
can be pretty messy. So, we let you turn it off or on:
PROCEDURE SetPrtSc (PrtScON: Boolean);
Use FALSE to turn it off or TRUE to turn it back on. If you turn
off PrtSc, you MUST remember to turn it back on again before
your program ends! Otherwise, an interrupt vector will point
into nowhere, causing probable chaos the next time PrtSc is
pressed.
Regardless of whether you've turned the PrtSc key off, you can
print the screen yourself just as if PrtSc had been pressed:
PROCEDURE PrintScreen;
Now here's a strange one for you. When IBM brought out the
101-key keyboard, called the "enhanced" keyboard, they did
something bizarre to the BIOS. They still allowed old keyboard
calls to work, but they filtered out the new key codes so no one
would see them. This made sure that no one would be able to use
the capabilities of the "enhanced" keyboard without rewriting
their programs. So, the keyboard has been around for years, and
there are still few programs that even notice when you press
F11. Pascal does not support the enhanced keyboard at all.
Fortunately, PasWiz -does-. You can find out if an enhanced
keyboard is installed with the EnhKbd function, which is in the
Equipment unit.
If there is an enhanced keyboard installed, you can activate it
with this:
PROCEDURE SetEnhKbd (Enhanced: Boolean);
With enhanced keyboard support activated, all key requests that
used the old services are translated to the new services. So,
SetEnhKbd affects the ReadKey (in the CRT unit that comes with
Pascal) and other Pascal functions as well as other PasWiz
keyboard routines. Note that you MUST deactivate enhanced
keyboard support before ending your program. Otherwise, an
interrupt vector will point into nowhere, probably causing a
crash on the next keypress!
Keyboard Control page 18
Unit: Keyboard
If you're about to request important input, you may not want to
chance having it answered from results of the keyboard buffer--
could be that the user meant those keys for another purpose. In
that case, it's a good approach to clear out the keyboard buffer
just before the input:
PROCEDURE ClearKbd;
No keyboard unit would be complete without a selection of
routines to check the shift states and get or set the keyboard
toggles. Let's start with the toggles, which are so called
because they get toggled from one state to another:
FUNCTION CapsOn: Boolean; { Caps Lock }
FUNCTION InsertOn: Boolean; { Insert }
FUNCTION NumOn: Boolean; { Num Lock }
FUNCTION ScrollOn: Boolean; { Scroll Lock }
You can also turn the toggles off or on. It's courteous to
restore the original toggle states once you end your program, so
you might want to save the original values for that purpose.
Then again, I guess that doesn't apply if your program is
designed for the specific purpose of setting the toggles!
PROCEDURE SetCaps (CapsLock: Boolean); { Caps Lock }
PROCEDURE SetInsert (InsertKey: Boolean); { Insert }
PROCEDURE SetNum (NumLock: Boolean); { Num Lock }
PROCEDURE SetScroll (ScrollLock: Boolean); { Scroll Lock }
Does anyone actually use ScrollLock for anything? Just
curious...
Keyboard Control page 19
Unit: Keyboard
The shift keys are unique in many respects. They don't return
codes that can be detected with ReadKey or stuffed into the
keyboard buffer; several can be pressed at the same time; and
they don't repeat. You can detect 'em with PasWiz, at any rate:
FUNCTION AltPress: Boolean; { any ALT }
FUNCTION CtrlPress: Boolean; { any CTRL }
FUNCTION ShiftPress: Boolean; { any SHIFT }
FUNCTION LAltPress: Boolean; { left ALT }
FUNCTION LCtrlPress: Boolean; { left CTRL }
FUNCTION LShiftPress: Boolean; { left SHIFT }
FUNCTION RAltPress: Boolean; { right ALT }
FUNCTION RCtrlPress: Boolean; { right CTRL }
FUNCTION RShiftPress: Boolean; { right SHIFT }
NOTE that LAltPress, LCtrlPress, RAltPress, and RCtrlPress are
ONLY available for enhanced keyboards. They will not return
useful results on older keyboards.
String Stuff page 20
Unit: Strings
Strings have always been something of an afterthought in Pascal.
This unit brings in the heavy artillery! The PasWiz string
routines may be divided into the following categories:
comparison, compression, encryption, extraction, searching, and
miscellaneous (mostly alteration). Let's take up these
categories one at a time.
The compression routines are designed to work with ordinary text
strings. The strings may not contain IBM extended-ASCII codes
(CHR($80)-CHR($FF)), as these are used in the compression.
Compression works on spaces, which will give you an average of
15% compression on normal text. If you compress printable text
(i.e., no control codes), you'll get printable text in return,
which makes these routines handy for use with text files.
Compression and decompression is done at an extreme rate of
speed and will not affect the timing of your program in any
noticeable fashion.
FUNCTION Bsq (St: String): String; { compress a string }
FUNCTION BUsq (St: String): String; { uncompress a string }
Comparing two strings is easy enough in Pascal, but not as
flexible as might be desired for some applications. You can tell
if two strings match exactly, but not if they're reasonably
close matches. PasWiz has two fuzzy comparison routines which
can help.
The Soundex routine uses a long-established algorithm to
convert a word into a code which represents the sound of the
word. It is fast and can work well under properly defined
circumstances; for instance, it will tell you that "Smith"
sounds just like "Smythe", but not like "Banks". Between the
need for speed and the vagaries of the English language,
however, you may find matches which don't work as well. Soundex
is certain that "Knight" sounds just like "Smashed", for
example. So, it may be perfect for something like a phone book,
but not for a spelling checker!
FUNCTION Soundex (St: String): String;
The Bickel routine, on the other hand, tells you how closely two
words match. This is a relative measure, not an absolute
measure, so you'd probably want to search through an entire
dictionary and just keep the words which matched best. The
Bickel algorithm is slower than Soundex but is much more
precise.
FUNCTION Bickel (St1, St2: String): Integer;
String Stuff page 21
Unit: Strings
There are many cases in which you might want to keep data
private. The PasWiz encryption routines provide a simple and
extremely fast method for password-protecting text. The
encryption technique is trivial and will not withstand any
concerted attack, but should suffice for casual use. It will
help if you pick a longish password of unusual characters, for
example '[^.mE@@&o}' (don't use this example itself, please)!
There are two encryption routines (which also work as decryption
routines). The first is designed to produce printable, if
bizarre-looking, results which are suited for use with text
files. Text to be encrypted by this routine may not include IBM
extended-ASCII characters (CHR($80) - CHR($FF)), since these are
used to insure printable text.
FUNCTION CipherP (St, Passwd: String): String;
The other routine will work with any sort of text, but is not
guaranteed to produce printable results. So, it's not suited for
use with text files.
FUNCTION Cipher (St, Passwd: String): String;
Of the extraction routines, two are merely convenient
rephrasings of the Copy function which allow you to grab a
substring from the left or right side of a string:
FUNCTION Left (St: String; Len: Integer): String;
FUNCTION Right (St: String; Len: Integer): String;
Another extraction routine is more interesting. It allows you to
extract a substring from a string which contains delimited
information. For instance, consider the following name and
address string. It has three fields, each separated by an
asterisk. We can use Extract to grab any individual field:
St := 'Tom Hanlin*3544 E. Southern Ave. #104*Mesa, AZ 85204';
FOR PrtAddress := 1 TO 3 DO BEGIN
AddressLine := Extract(St, '*', PrtAddress);
WriteLn(AddressLine);
END;
The extract function is defined as follows:
FUNCTION Extract (St, Delimiter: String;
Index: Integer): String;
If you try to extract a field which doesn't exist, a null string
will be returned. Field delimiters may be anything at all, by
the way-- one possible use for Extract might be to block-read a
text file, splitting it apart in memory by using the carriage
return and linefeed as a delimiter.
String Stuff page 22
Unit: Strings
PasWiz provides an assortment of string search routines. One is
a simple modification of Pos which allows you to start the
search at a given place in the string:
FUNCTION Instr (Start: Integer; SubSt, St: String): Integer;
Another works like Pos, but returns the last match rather than
the first match:
FUNCTION RPos (SubSt, St: String): Integer;
Of course, you don't always want to search for something in
specific. You might be more interested in finding the first
character that fits into a given category or categories, like
perhaps numbers or letters. No problem:
FUNCTION TypePos (ChType: Integer; St: String): Integer;
The ChType value is formed by adding the numbers which represent
the desired categories. This can also be used to search for the
first character which is not of a given type, since the
categories are exclusive:
1 alphabetic
2 numeric
4 symbols
8 control codes (ASCII 0-31, 127)
16 graphics codes (ASCII 128-255)
32 space (ASCII 32)
That covers the string routines which can be readily
categorized. Most of the remaining routines are designed to
alter a string in one way or another. This includes being able
to remove selected characters, substrings, types of characters,
or repeated characters from a string; trimming the left or right
side of a string of blanks; converting to uppercase, lowercase,
or using the correct capitalization for a proper name; replacing
one substring with another; reversing a string; and forming a
new string by repeating a given substring.
The "case", "trim", and "reverse" functions are pretty much
self-explanatory:
FUNCTION LowerCase (St: String): String; { lowercase }
FUNCTION NameCase (St: String): String; { name case }
FUNCTION UpperCase (St: String): String; { upper case }
FUNCTION LTrim (St: String): String; { left trim }
FUNCTION RTrim (St: String): String; { right trim }
FUNCTION Reverse (St: String): String; { reverse }
String Stuff page 23
Unit: Strings
The Crunch function removes adjacent repetitions of a substring.
This is particularly handy for parsing user input, for example--
you can use it to remove repeated spaces between options, etc.
FUNCTION Crunch (SubSt, St: String): String;
The Dupe function forms a string by repeating a substring. It's
handy when you need a string of a given number of spaces,
zeroes, or nulls. It can also be used for drawing horizontal
lines and other applications.
FUNCTION Dupe (Count: Integer; SubSt: String): String;
There are many times when it's nice to be able to replace all
occurrences of one substring with another. PasWiz can do that,
no problem. You don't have to worry about recursion, either--
each occurrence is replaced only once, even if the replacement
substring contains the target substring.
FUNCTION Replace (OldSubSt, NewSubSt, St: String): String;
Of course, you can delete a substring by replacing it with a
null string. It's faster to use the routine designed for that
purpose, though:
FUNCTION StripSt (SubSt, St: String): String;
You may also delete specified characters from a string:
FUNCTION StripCh (ChList, St: String): String;
Next, a routine which deletes specified types of characters from
a string. This can be very good for screening user input.
FUNCTION StripType (ChType: Integer; St: String): String;
The ChType value is formed by adding the numbers which represent
the desired categories:
1 alphabetic
2 numeric
4 symbols
8 control codes (ASCII 0-31, 127)
16 graphics codes (ASCII 128-255)
32 space (ASCII 32)
PasWiz also has a specialized variant on the Val function which
converts a number from a string to a WORD (or INTEGER) value. No
error checking is provided. This is very small and fast:
FUNCTION WVal (St: STRING): WORD;
Music page 24
Unit: Music
Granted, the PC has never been known for its wonderful sound
capabilities. Still, it has more potential than you might guess,
especially given the utterly minimal Sound/Delay/NoSound
routines that are provided with Pascal. The Music unit makes it
much easier to use sounds by providing an actual music language.
The BACHINV and ENTERTNR demo programs demonstrate some of the
possibilities.
The PasWiz music language is nearly identical to that offered by
the BASIC PLAY statement. The major difference is that the "MB"
command (play the music as a background task) is not supported.
I'll add that later, if I get enough requests.
There are only two music procedures:
PROCEDURE ResetMF;
PROCEDURE PlayMF(Sounds: String);
The ResetMF procedure is used to reset all music parameters to
the default values. It would typically be used after finishing a
song to restore the music handler for the next song.
The PlayMF procedure is the one that does the real work. Here's
a list of the music commands supported:
MB play music as a background task (ignored)
MF play music as a foreground task (ignored)
ML legato (8/8 note length)
MN normal music (7/8 note length)
MN staccato (6/8 note length)
Ln Length of notes (n = 1-64; note length = 1/n)
Nn Note number (n = 0-84; 0 is a rest)
On Octave (n = 0-6, default 4)
Pn Pause (n = 1-64; pause length = 1/n)
Tn Tempo (n = 32-255, default 120; quarter notes/minute)
< move up an octave (max 6)
> move down an octave (min 0)
You can also use the actual letters of the notes (C, D, E, F, G,
A, and B). If you're not particularly musical, these correspond
to "do, re, mi, fa, so, la, ti" (with the final "do" being C
again, but an octave higher). To play a scale, you'd use
'CDEFGAB>C'. The notes may be followed by dots, by note lengths,
and by sharp or flat symbols (a '+' or '#' for a sharp, a '-'
for a flat). For example, 'D-.' is a dotted D flat. The dot
means that the note will play for half again its usual length.
Dots can be repeated.
I might note (ahem) that the so-called "ANSI" music offered by
some BBSes and comm programs is based on this same music
language. If you're writing telecomm software, this unit makes
it trivial to add sound.
Mouse page 25
Unit: Mouse
Considering the ubiquity of the mouse these days, it's a marvel
to me that none of the popular programming languages provides
any support for it. Well, BASIC supports it via light pen
emulation, but that scarcely counts. Anyway, this is a simple
little unit which provides you direct access to the mouse
driver. It will work with any Microsoft-compatible mouse driver.
A few words are necessary on mouse handling. To begin with, the
best time to check for a mouse is immediately after you've
established the desired video mode. The mouse driver initializes
certain information when you check for it, so it's a good idea
to check for it only once, at the appropriate time.
The mouse cursor was implemented in a rather bizarre manner. If
you call ShowCursor, you're guaranteed that the mouse cursor
will be visible; however, this also increments an internal
visibility flag. If you call ShowCursor multiple times, you will
also have to call HideCursor multiple times before the dang
cursor actually disappears.
The mouse driver was apparently implemented without any thought
for the future. In text mode, it returns coordinates based on
CGA hi-res graphics mode-- 640x200 instead of the expected
80x25. If you use the mouse driver in text mode, you'll have to
compensate for this (divide coordinates by 8 to convert to text
mode, or multiply by 8 to convert to the mouse virtual mode).
The CGA low-res 320x200 graphics mode is implemented likewise.
It seems Microsoft thought that 640x200 would be the ultimate
resolution, so they translated everything to a 640x200 virtual
mode. Arrgh. Fortunately, this does not apply to video modes
other than text and CGA graphics.
I understand that it's possible to get the mouse working in
Hercules graphics mode, but I'm not sure what the details are.
If anyone out there knows, please let me know, and I'll pass it
along.
Since Microsoft was initially very tight-lipped about the mouse
functions, older mouse drivers for Microsoft-compatible rodents
don't necessarily have all the functions available (this
includes Logitech, amazingly enough). The Microsoft mouse driver
continues to be updated in a rapid and haphazard manner, so
keeping up-to-date is no guarantee. However, if you have any
problems with these routines, you can almost certainly clear
them up by getting a newer mouse driver. The routines in this
unit are almost all old, well-established functions. "Iffy" ones
are marked for your convenience.
Mouse page 26
Unit: Mouse
The first function to consider simply tells you whether a mouse
is available, and if so, how many buttons it has. It also resets
the mouse driver, so it should be used only at the beginning of
your program (or after changing the video mode).
FUNCTION Init: Integer;
{ returns 0 if no mouse, else # of buttons }
Most often, you'll want to know which mouse button(s) are
pressed and where the mouse cursor is:
FUNCTION LeftButton: Boolean;
{ TRUE if pressed }
FUNCTION MidButton: Boolean;
{ always FALSE on 2-button rodents }
FUNCTION RightButton: Boolean;
{ TRUE if pressed }
FUNCTION WhereX: Integer;
{ X-coord (see notes on previous page) }
FUNCTION WhereY: Integer;
{ Y-coord (see notes on previous page) }
As well as finding the current mouse information, it's possible
to find out what the mouse has been doing since you last
checked. Two sets of routines exist: one to find out how many
times a button was pressed and where it was at the last press,
and one to find out how many times a button was released and
where it was at the last release.
PROCEDURE LeftClick(VAR Count, X, Y: Integer);
PROCEDURE MidClick(VAR Count, X, Y: Integer);
PROCEDURE RightClick(VAR Count, X, Y: Integer);
PROCEDURE LeftRelease(VAR Count, X, Y: Integer);
PROCEDURE MidRelease(VAR Count, X, Y: Integer);
PROCEDURE RightRelease(VAR Count, X, Y: Integer);
Mouse page 27
Unit: Mouse
There's one more informational routine. It tells you the basic
mouse software and hardware stats: driver version, connector
type (serial, bus, etc), and IRQ used. This routine is slightly
hazardous in that, although Microsoft claims it has existed from
the first, other manufacturers (including Logitech) have taken
some time to implement it. In other words, it may not return
useful information. The routine conducts a self-check and will
return all zeroes if it suspects the information is not good,
but even so... proceed with care.
PROCEDURE Info(VAR Version: Real; VAR Connector, IRQ: Byte);
Connector info: 1=bus, 2=serial, 3=InPort, 4=PS/2,
5=Hewlett-Packard IRQs: 0=PS/2; 2-5, 7 are actual IRQ numbers.
It is conceivable that higher IRQ numbers may be supported on
AT-type machines, though not by Microsoft.
Of course, you can set mouse info as well as retrieving it. The
following routines are pretty well self-explanatory (don't
forget the notes about the strange text-mode and CGA coordinate
handling, and cursor handling):
{ hide the mouse cursor }
PROCEDURE HideCursor;
{ (maybe) make the cursor visible }
PROCEDURE ShowCursor;
{ set the cursor position }
PROCEDURE GotoXY(X, Y: Integer);
{ set the cursor range }
PROCEDURE Window(X1, Y1, X2, Y2: Integer);
There are other possibilities supported by the mouse driver that
I have not implemented here. They're mostly on the rather
esoteric side and are not supported by anything except recent
Microsoft mouse drivers and perhaps some close compatibles, such
as Logitech.
Among the mouse capabilities I haven't implemented are finding
out how fast and in what direction the mouse is moving, the
coordinate size of the screen as far as the mouse is concerned,
finding out the video modes that the mouse driver understands,
setting mouse speed and sensitivity parameters, setting up an
interrupt-driven system for handling mouse events, and a few
others.
Mouse page 28
Unit: Mouse
None of these missing capabilities is particularly vital (or
even useful) for the average mouse application. However, if
you're interested in any of these capabilities, let me know
which ones, and I'll be glad to add them to PasWiz.
Note: if you're confused at how I can get away with duplicating
the names of existing procedures... well, it's easy enough.
Let's consider WhereX. It means one thing to Pascal's Crt unit
and another to the PasWiz Mouse unit. Since the Mouse unit
doesn't use the Crt unit, it has no problem with any ambiguity.
Existing programs that don't use the Mouse unit will likewise
have no problems, since they don't know about the new routine.
Suppose you want to use both the Crt and Mouse units together,
however? There's the rub!
You can specify a routine from a given unit by including the
unit name when you use the routine. For instance:
X := Mouse.WhereX; { get the mouse cursor position }
X := Crt.WhereX; { get the normal cursor position }
This allows multiple units to use the same names for things.
While this may seem like a bit of a pain, since you have to
specify which you want, it has a major built-in advantage if
used properly. What's properly? When a routine in one unit works
just like a routine by the same name in another unit. When I
call the mouse routine "WhereX", for instance, I've also told
you what it is for and how to use it. So, the name itself
contains a lot of useful information on how to use the routine!
That's what I call a major advantage.